package com.googlecode.jerato.library.store;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.googlecode.jerato.core.FunctionParameters;
import com.googlecode.jerato.core.FunctionTransfer;
import com.googlecode.jerato.core.store.StoreFunction;
import com.googlecode.jerato.core.store.StoreParameters;
import com.googlecode.jerato.core.store.StoreTransaction;
import com.googlecode.jerato.core.store.StoreTransfer;
import com.googlecode.jerato.library.SystemException;

public class StoreFunctionImpl implements StoreFunction{
	public static final String KEY_RESULT_LIST = "__result_list";
	public static final String KEY_RESULT_EXISTS = "__result_exists";
	public static final String KEY_RESULT_FIRST = "__result_first";
	public static final String KEY_RESULT_COUNT = "__result_count";
	public static final String KEY_UPDATE_COUNT = "__update_count";

	protected String _storeSql;
	protected boolean _updateSql;
	protected List _bindNameList = new ArrayList();

	protected StoreFunctionImpl(){
	}

	public void call(FunctionTransfer trans, FunctionParameters input, FunctionParameters output){
		if(!(trans instanceof StoreTransfer)
			|| !(input instanceof StoreParameters)
			|| !(output instanceof StoreParameters))
		{
			trans = new StoreTransferImpl(trans);
			input = new StoreParametersImpl(input);
			output = new StoreParametersImpl(output);
		}
		execute((StoreTransfer)trans, (StoreParameters)input, (StoreParameters)output);
		((StoreParameters)output).flushParent();
		((StoreTransfer)trans).flushParent();
	}

	public void execute(StoreTransfer trans, StoreParameters input, StoreParameters output){
		if(trans == null){
			throw new IllegalArgumentException("trans is null.");
		}
		StoreTransaction transaction = trans.getTransaction();
		if(transaction == null){
			throw new SystemException("Transaction not initialized.");
		}

		if(!_updateSql){
			List resultList = executeSelect(transaction.getConnection(), _storeSql, _bindNameList, input);
			output.put(KEY_RESULT_LIST, resultList);
			output.put(KEY_RESULT_EXISTS, (resultList.size() > 0? Boolean.TRUE: Boolean.FALSE));
			output.put(KEY_RESULT_FIRST, (resultList.size() > 0? resultList.get(0): null));
			output.put(KEY_RESULT_COUNT, new Integer(resultList.size()));
		}
		else{
			int updateCount = executeUpdate(transaction.getConnection(), _storeSql, _bindNameList, input);
			output.put(KEY_UPDATE_COUNT, new Integer(updateCount));
		}
	}

	protected List executeSelect(Connection conn, String sql, List bindNameList, Map bindValues){

		PreparedStatement stmt = null;
		ResultSet resSet = null;
		try{
			try{
				stmt = conn.prepareStatement(sql);
				
				if(bindNameList != null && bindValues != null){
					processStatement(stmt, bindNameList, bindValues);
				}

				resSet = stmt.executeQuery();
				ArrayList vec = new ArrayList();
				ResultSetMetaData meta = resSet.getMetaData();
				final int count = meta.getColumnCount();

				String[] colNameList = new String[count];
				for(int loop = 0; loop < count; loop++){
					colNameList[loop] = meta.getColumnName(loop + 1);
				}

				prepareResultSet(resSet, colNameList);

				while(resSet.next()){
					Map bean = new HashMap();
					bean = processResultSet(bean, colNameList, count, resSet);
					vec.add(bean);
				}
				return vec;
			}
			finally{
				try{
					if(resSet != null){
						resSet.close();
					}
				}
				finally{
					if(stmt != null){
						stmt.close();
					}
				}
			}
		}
		catch(SQLException se){
			throw new SystemException("Store select failed.", se);
		}
	}

	protected int executeUpdate(Connection conn, String sql, List bindNameList, Map bindValues){

		PreparedStatement stmt = null;
		try{
			try{
				stmt = conn.prepareStatement(sql);
				
				if(bindNameList != null && bindValues != null){
					processStatement(stmt, bindNameList, bindValues);
				}

				int updateaCount = stmt.executeUpdate();
				return updateaCount;
			}
			finally{
				if(stmt != null){
					stmt.close();
				}
			}
		}
		catch(SQLException se){
			throw new SystemException("Store update failed.", se);
		}
	}

	protected void prepareResultSet(ResultSet resSet, String[] colNameList){
		for (int i = 0; i < colNameList.length; i++) {
			colNameList[i] = colNameList[i].toLowerCase();
		}
	}
	protected void processStatement(PreparedStatement stmt, List bindNameList, Map bindValues)
		throws SQLException{

		for(int loop = 0; loop < bindNameList.size(); loop++){
			String name = (String)bindNameList.get(loop);
			stmt.setObject(loop + 1, bindValues.get(name));
		}
	}
	protected Map processResultSet(Map bean, String[] colNameList, int colCount, ResultSet resSet)
		throws SQLException{

		for(int index = 0; index < colCount; index++){
			bean.put(colNameList[index], resSet.getObject(index + 1));
			bean.put(String.valueOf(index), resSet.getObject(index + 1));
		}
		return bean;
	}
}
